package model;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Random;

public class Computation {
    private int lowerBound;
    private int upperBound;
    private int populationSize;
    private int sampleSize;
    private String graphType;
    
    private ArrayList<Double> population;
    private ArrayList<Double> populationMeans;
    private double populationMean;
    private double populationVariance;
    
    private ArrayList<ArrayList<Double>> samples;
    private ArrayList<Double> sampleMeans; //means for each sample
    private ArrayList<Double> uniqueSampleMeans;
    private ArrayList<Double> functionX;
    private double sampleMean; 
    private double sampleVariance;
    
    private ArrayList<Double> uniquePopulation;
    
    
    public static final int MAX_X = 1000;
    
    public Computation(int lowerBound, int upperBound, int populationSize, int sampleSize, String graphType) {
        this.lowerBound = lowerBound;
        this.upperBound = upperBound;
        this.populationSize = populationSize;
        this.sampleSize = sampleSize;
        this.graphType = graphType;
        
        genericComputation();
    }
    
    public void genericComputation(){
    	
    	populatePopulation();
    	Collections.sort(population);
    	
    	System.out.println("POPULATION is : " + population);
    
    	switch(graphType.toLowerCase()){
    	
    	case "uniform": computePopulationMean(); computeUniformProbability();
    		break;
    	case "random" :  computePopulationMean(); computeRandomProbability();
    		break;
    	case "normal" :  computePopulationMean(); computeProbability(populationMean);
    		break;
    	case "skewed right" : computeProbability(population.get(population.size()-1));
    		break;
    	case "skewed left" : computeProbability(population.get(0));
    		break;
    	case "bimodal" : computeBimodalDistribution();
    		break;
    	}
    	
    	ArrayList<ArrayList<Double>> outer = new ArrayList<ArrayList<Double>>();
     	ArrayList<ArrayList<Double>> output = new ArrayList<ArrayList<Double>> ();
 		
     	ArrayList<Double> temp;
    	
    	if(sampleSize == 1){
    		
    		for(Double d : population){
    			temp = new ArrayList<Double>();
    			temp.add(d);
    			outer.add(temp);
    		}
    		samples = outer;
    	}
    	else{
    
     	outer.add(new ArrayList<Double>());
 		output = populateCombinations(outer, sampleSize);
 		samples = output;
    	}
    	
 		computeSampleMeans();
        MeanofSampleMeans();

        VarianceofSampleMeans();
    	
    }
    
    public void populatePopulation(){
    	
    	population = new ArrayList<Double>();
		for(int i = lowerBound; i<= upperBound; i++)
			population.add((double) i);
    	
    }
    
    public void computeBimodalDistribution(){
    	ArrayList<Double> left = new ArrayList<>();
    	ArrayList<Double> right = new ArrayList<>();
    	ArrayList<Double> leftProbability = new ArrayList<>();
    	ArrayList<Double> rightProbability = new ArrayList<>();
    	ArrayList<Double> probability = new ArrayList<>();
    	double mid = (upperBound + lowerBound) / 2.0;
    	double value;
    	double leftMean = 0;
    	double rightMean = 0;
    	double leftVariance = 0;
    	double rightVariance = 0;
    	
    	population = new ArrayList<Double>();
    	Random rand = new Random();

    	for(int i = lowerBound; i <= upperBound; i++){
    		value = i;   		
    		if(mid < value)
    			right.add(value);
    		else
    			left.add(value);
    	}
       	
    	Collections.sort(left);
    	Collections.sort(right);
    	
    	for(Double d : left)
    		leftMean += d;
    	
    	for(Double d : right)
    		rightMean += d;
    	
    	leftMean = leftMean / left.size() * 1.0;
    	rightMean = rightMean / right.size() * 1.0;
       	
    	population.addAll(left);
    	population.addAll(right);
    	
    	for(double d : left)
    		leftVariance = leftVariance + Math.pow((d - leftMean),2);
    	
    	for(double d : right)
    		rightVariance = rightVariance + Math.pow((d - rightMean),2);
    	
    	leftVariance = leftVariance / left.size();
    	rightVariance = rightVariance / right.size();
    			
    	for(Double x : left){
    		Double y = (1/Math.sqrt(2*Math.PI* leftVariance))*Math.pow(Math.E, -(Math.pow((x-leftMean), 2))/(2*leftVariance))* populationSize;
    		leftProbability.add(y);
    	}
    	
    	for(Double x : right){
    		Double y = (1/Math.sqrt(2*Math.PI* rightVariance))*Math.pow(Math.E, -(Math.pow((x-rightMean), 2))/(2*rightVariance))* populationSize;
    		rightProbability.add(y);
    	}
    	
    	probability.addAll(leftProbability);
    	probability.addAll(rightProbability);
    	
    	populationMean = (leftMean + rightMean) / 2.0; 
    	populationMeans = probability;
    	
    	computePopulationVariance();

    }
    
    public ArrayList<Double> computeProbability(double populationMean){
    	
    	ArrayList<Double> temp = new ArrayList<>();
    	
    	this.populationMean = populationMean;
    	this.populationVariance = computePopulationVariance();
    	    	
    	ArrayList<Double> normalDistribution  = new ArrayList<>();
    	
    	for(Double x : population){
    		Double y = (1/Math.sqrt(2*Math.PI* populationVariance))*Math.pow(Math.E, -(Math.pow((x-populationMean), 2))/(2*populationVariance)) * populationSize;
    		//y = y / populationSize;
    		normalDistribution.add(y);
    	}
    	
    	populationMeans = normalDistribution;
    	
    	Double a = 0.0;
    	for(Double d : normalDistribution)
    		a += d;
    	
    	System.out.println("POPULATION MEANS ARE " + a);
    	//System.out.println("POPULATION MEANS ARE " + populationMeans);
    	
    	return temp = populationMeans;
    }

    public ArrayList<Double> computeRandomProbability(){
    	computePopulationVariance();
    	
    	ArrayList<Double> randomProbability = new ArrayList<Double>();
    	Random rand = new Random();
    	int value = populationSize;
    	
    	int [] containers = new int [population.size()];
    	
    	for(int i = 0; i < populationSize; i++){
    		int y = rand.nextInt(((population.size()-1) - 0) + 1) + 0;
    		containers[y] = containers[y] + 1;
    	}
    	
    	for(int k = 0; k < containers.length; k ++){
    		randomProbability.add(containers[k]* 1.0);
    	}
    	
    	System.out.println(randomProbability);
    	
    	populationMeans = randomProbability;
    	
    	return populationMeans;
    }
    
    public ArrayList<Double> computeUniformProbability(){
    	computePopulationVariance();
    	
    	ArrayList<Double> uniformProbability = new ArrayList<Double>();
    	int probability = populationSize / population.size();
    	
    	for(Double d: population)
    		uniformProbability.add(probability*1.0);
    	
    	System.out.println(uniformProbability);
    	
    	populationMeans = uniformProbability;
    	
    	return populationMeans;
    }
      
    public void computePopulationMean()
    {
    	double temp, funcX;
    	int count;
    	double probability = 1.0f/ population.size();
    	
    	populationMeans = new ArrayList<Double>();
    	
    	for(int i=0 ; i< population.size() ; i++)
    	{
    		count = 0;
    		temp = population.get(i);
    		
    		for(int j=0;j<population.size();j++)
    			if(temp == population.get(j))
    			{
    				count++;
    			}
    		
    		funcX = count*probability;
    		
    		populationMeans.add(funcX);
    		populationMean += (temp*funcX);
    	}
    	
    	
    	double myMean = 0.0;
    	for(double d : population)
    		myMean += d;
    	
    	//System.out.println("%%%" + populationMean);
    	//System.out.println("myMean" + myMean/population.size());
  
    	
    	populationMean = myMean/population.size();
    	
    	
    	//System.out.println("Means : "+ populationMeans);
		System.out.println("POPULATION MEAN: "+ populationMean);
		
    }
    
    public double computePopulationVariance(){ 

    	double myVariance = 0.0;
    	for(double d : population)
    		myVariance = myVariance + Math.pow((d - populationMean),2);
    	
    	populationVariance = myVariance/population.size();
    	
    	System.out.println("POPULATION VARIANCE " + populationVariance);
    	
    	//System.out.println("myVariance is " + myVariance);
    	//System.out.println("myVariance / popSize is " + myVariance/population.size());
    	return populationVariance;
    }

    public ArrayList<ArrayList<Double>> populateCombinations(ArrayList<ArrayList<Double>> list, int subsetSize){
		
    	ArrayList<ArrayList<Double>> sample = new ArrayList<ArrayList<Double>> ();
		ArrayList<Double> combination = new ArrayList<>();
		ArrayList<Double> temp = new ArrayList<>();
		boolean populate = false;
	
		if(list.get(0).isEmpty()){
			temp = population;
			populate = true;
		}
		else if (list.get(0).size() == subsetSize){
		    populate = false;
			return list;
		}
		else if (list.get(0).size() < subsetSize) {
			temp = list.get(0);
			populate = false;
		}
	
		if(populate){
			for(int i = 0; i < temp.size(); i++){
				for(int j = 0; j < temp.size(); j++){
				combination = new ArrayList<>();
			    combination.add(temp.get(i));
				combination.add(temp.get(j));
				sample.add(combination);
				}
			}
			
			populate = false;
			return populateCombinations(sample, subsetSize);
		}
		else{
			ArrayList<ArrayList<Double>> duplicate = new ArrayList<ArrayList<Double>>();
			ArrayList<ArrayList<Double>> original = new ArrayList<ArrayList<Double>>();
			ArrayList<Double> myTemp;
			original = list;
		
			for(int i = 0 ; i < population.size(); i++){
				for(ArrayList<Double> a : original){
					myTemp = new ArrayList<Double>();
					myTemp.addAll(a);
					myTemp.add(population.get(i));
					duplicate.add(myTemp);
				}
			}
			sample = duplicate;
			return populateCombinations(sample, subsetSize);
		}
		
	}
    
    public void computeSampleMeans()
    {
    	double probability = 1.0f/sampleSize;
    	double mean;
    	int sum;
    	
    	sampleMeans = new ArrayList<Double>();
    	
    	for(int i = 0; i < samples.size(); i++)
    	{
    		sum = 0;
    		for(int j = 0; j < samples.get(i).size(); j++)
    		{
    			//System.out.print(samples.get(i).get(j)+", ");
    			sum += samples.get(i).get(j);
    		}
			//System.out.println();
			mean = (sum*probability);
			sampleMeans.add(mean);
			//System.out.println("Sample Means: "+sampleMeans.get(i));
			
			//Collections.sort(sampleMeans);
    	}    	
    }
    
    public ArrayList<Double> getUniqueSampleMeans()
    {
    	ArrayList<Double> uniqueList = new ArrayList<Double>();
    	
    	for(int i = 0; i < sampleMeans.size(); i++)
    	{
    		if(!uniqueList.contains(sampleMeans.get(i)))
    			uniqueList.add(sampleMeans.get(i));
    	}
    	
    	Collections.sort(uniqueList);
    	
    	return uniqueList;
    }
    
    public void MeanofSampleMeans()
    {	
    	double temp, funcX;
    	int count;
    	double probability = 1.0f/samples.size();
    	
    	uniqueSampleMeans = new ArrayList<Double>();
    	functionX = new ArrayList<Double>();
    	uniqueSampleMeans = getUniqueSampleMeans();
    	
    	Collections.sort(uniqueSampleMeans);

		for(int i = 0 ; i < uniqueSampleMeans.size() ; i++)
		{
			temp = uniqueSampleMeans.get(i);
			count = 0;
			for(int j = 0 ; j < sampleMeans.size(); j++)
			{
				if(temp == sampleMeans.get(j))
					count++;
			}
			
			funcX = count*probability; 
			
			//System.out.println("funcX is " + funcX);
    		sampleMean += (temp*funcX);
    		functionX.add(funcX); // saves the f(x) for unique set of means
		}
		
		//System.out.println(uniqueSampleMeans);
		
		System.out.println("SAMPLE MEANS "+ sampleMean);
    }
    
    public void VarianceofSampleMeans()
    {

    	double probability = 1.0f/sampleSize;
    	double temp = 0;
    	
    	for(int i=0; i<uniqueSampleMeans.size();i++)
    	{
    		temp += (getSquare(uniqueSampleMeans.get(i))*functionX.get(i));
    	}
    	
    	
    	sampleVariance = temp - getSquare(sampleMean);
    	
    	System.out.println("SAMPLE VARIANCE " + sampleVariance);
    }
    
    
    //getters and setters
    public double getSquare(double n){
    	return n*n;
    }
    
    public ArrayList<Double> getPopulation() {
		return population;
	}
	
    public void setPopulationSize(int populationSize) {
		this.populationSize = populationSize;
	}

	public void setSampleSize(int sampleSize) {
		this.sampleSize = sampleSize;
	}

	public ArrayList<Double> getPopulationMeans() {
		return populationMeans;
	}

	public double getPopulationMean() {
		return populationMean;
	}

	public double getPopulationVariance() {
		return populationVariance;
	}

	public ArrayList<ArrayList<Double>> getSamples() {
		return samples;
	}

	public ArrayList<Double> getUniqueMeanList() {
		return uniqueSampleMeans;
	}

	public double getSampleMean() {
		return sampleMean;
	}

	public double getSampleVariance() {
		return sampleVariance;
	}
	
    public ArrayList<Double> getSampleMeans() {
		return sampleMeans;
	}
    public int getPopulationSize()
    {
    	return populationSize;
    }
    public ArrayList<Double> getfunctionX()
    {
    	return functionX;
    }
}

